/*
 * Copyright (c) Zhongkehonglue Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Implementation of functions related to the DMC DEV module.
 * Author: wangchunyang
 * Create: 2025-05-23
 */

#include "dmcParseXml.h"

#include <stdlib.h>
#include <securec.h>
#include "upnp.h"
#include "upnpapi.h"

#include "dmcLog.h"
#include "upnptools.h"

#ifdef LOG_TAG
#undef LOG_TAG
#endif
namespace DlnaDmc {

bool DmcIsDmrDevice(const std::string &deviceType)
{
    if (0 == memcmp(MEDIA_RENDERER_GEN_DEVICE_TYPE, deviceType.c_str(),
        strlen(MEDIA_RENDERER_GEN_DEVICE_TYPE))) {
        return true;
    }
    return false;
}

const char* DmcGetXmlElementValue(IXML_Element *parent, const char *tagName)
{
    if ((parent == nullptr) || (tagName == nullptr)) {
        LOGE("Invalid Parameter has been"
            "Passed to DlnaElementGetChildElementValue");
        return nullptr;
    }

    IXML_NodeList *nodeList = ixmlElement_getElementsByTagName(parent, tagName);
    if (nodeList == nullptr) {
        return nullptr;
    }

    IXML_Node *elementNode = ixmlNodeList_item(nodeList, 0);
    ixmlNodeList_free(nodeList);
    if (elementNode == nullptr) {
        return nullptr;
    }

    IXML_Node *textNode = ixmlNode_getFirstChild(elementNode);
    if ((textNode == nullptr) || (ixmlNode_getNodeType(textNode) != eTEXT_NODE)) {
        return nullptr;
    }

    return ixmlNode_getNodeValue(textNode);
}

const char* DmcGetXmlElementValueFromDoc(IXML_Document *parent, const char *tagName)
{
    if ((parent == nullptr) || (tagName == nullptr)) {
        LOGE("nullptr");
        return nullptr;
    }

    IXML_NodeList *nodeList = ixmlDocument_getElementsByTagName(parent, tagName);
    if (nodeList == nullptr) {
        return nullptr;
    }

    IXML_Node *elementNode = ixmlNodeList_item(nodeList, 0);
    ixmlNodeList_free(nodeList);
    if (elementNode == nullptr) {
        return nullptr;
    }

    IXML_Node *textNode = ixmlNode_getFirstChild(elementNode);
    if ((textNode == nullptr) || (eTEXT_NODE != ixmlNode_getNodeType(textNode))) {
        return nullptr;
    }

    return ixmlNode_getNodeValue(textNode);
}

const char* DmcParsebaseURL(IXML_Document* devDescripeDoc)
{
    IXML_NodeList* urlList;
    const char* baseURL   = nullptr;

    if (nullptr == devDescripeDoc) {
        return nullptr;
    }

    urlList = ixmlDocument_getElementsByTagName(devDescripeDoc, "URLBase");
    if (nullptr != urlList) {
        IXML_Node* urlNode = ixmlNodeList_item(urlList, 0);
        if (nullptr != urlNode) {
            IXML_Node* textNode = ixmlNode_getFirstChild(urlNode);
            if ((nullptr != textNode) && (nullptr != ixmlNode_getNodeValue(textNode))) {
                baseURL = ixmlNode_getNodeValue(textNode);
            }
        }

        ixmlNodeList_free(urlList);
    }

    return baseURL;
}

DmcServiceType DmcGetServiceEnumType(char* serviceId)
{
    char* tmpStr = nullptr;

    if (nullptr == serviceId) {
        return DMC_SERVICE_INVALID;
    }

    tmpStr = strrchr((char*)serviceId, ':');
    if (nullptr == tmpStr) {
        tmpStr = strrchr((char*)serviceId, '-');
        if (nullptr == tmpStr) {
            LOGE("The Service Type is not valid. "
                "Received service ID-%{public}s ", serviceId);
            return DMC_SERVICE_INVALID;
        }
    }

    if (0 == memcmp("ContentDirectory", (tmpStr + 1), strlen("ContentDirectory"))) {
        return DMC_SERVICE_CDS;
    } else if (0 == memcmp("ConnectionManager", (tmpStr + 1), strlen("ConnectionManager"))) {
        return DMC_SERVICE_CMS;
    } else if (0 == memcmp("AVTransport", (tmpStr + 1), strlen("AVTransport"))) {
        return DMC_SERVICE_AVT;
    } else if (0 == memcmp("RenderingControl", (tmpStr + 1), strlen("RenderingControl"))) {
        return DMC_SERVICE_RCS;
    } else if (0 == memcmp("ScheduledRecording", (tmpStr + 1), strlen("ScheduledRecording"))) {
        return DMC_SERVICE_SRS;
    } else if (0 == memcmp("X_CTC_RemoteControl", (tmpStr + 1), strlen("X_CTC_RemoteControl"))) {
        return DMC_SERVICE_XCRCS;
    }

    return DMC_SERVICE_EXT;
}


dmc_int32 DmcGetServiceIdAndType(IXML_Element* serviceEle, DmcServiceInfo &serviceInfo)
{
    DmcServiceType enServiceType;
    const char* serviceID;
    const char* serviceType;

    serviceID = DmcGetXmlElementValue(serviceEle, "serviceId");
    if (nullptr == serviceID) {
        LOGE("Failed to get ServiceID");
        return DMC_FAILURE;
    }
    serviceInfo.serviceId = serviceID;
    LOGI("serviceId=%{public}s",
         serviceID);

    serviceType = DmcGetXmlElementValue(serviceEle, "serviceType");
    if (nullptr == serviceType) {
        LOGE("Failed to get ServiceType");
        return DMC_FAILURE;
    }
    serviceInfo.serviceType = serviceType;
    LOGI("serviceType=%{public}s",
         serviceType);

    enServiceType = DmcGetServiceEnumType((char*)serviceID);
    serviceInfo.servType = enServiceType;
    return DMC_SUCCESS;
}

dmc_int32 DmcGetControlUrlAndEventSubUrl(IXML_Element* serviceEle, const std::string &baseURL,
    DmcServiceInfo &serviceInfo)
{
    const char* szEventSubURL = DmcGetXmlElementValue(serviceEle, "eventSubURL");
    if (szEventSubURL != nullptr) {
        LOGI("szEventSubURL=%{public}s", szEventSubURL);
        auto buf = std::make_unique<char[]>(strlen(baseURL.c_str()) + strlen(szEventSubURL) + 2);
        dmc_int32 iRes = UpnpResolveURL(baseURL.c_str(), szEventSubURL, (char*)buf.get());
        if (UPNP_E_SUCCESS != iRes) {
            LOGE("Resolve of the SCPDURL failed with = %ld", iRes);
            return DMC_FAILURE;
        }
        serviceInfo.eventURL = buf.get();
        LOGI("buf.get()=%{public}s",
             buf.get());
    } else {
        LOGE(" event sub url null");
    }

    const char* controlUrl = DmcGetXmlElementValue(serviceEle, "controlURL");
    if (controlUrl == nullptr) {
        LOGE(" nullptr");
        return DMC_FAILURE;
    }
    LOGI(" controlUrl=%{public}s", controlUrl);
    auto buf = std::make_unique<char[]>(strlen(baseURL.c_str()) + strlen(controlUrl) + 2);
    dmc_int32 iRes = UpnpResolveURL(baseURL.c_str(), controlUrl, (char*)buf.get());
    if (UPNP_E_SUCCESS != iRes) {
        LOGI(" buf.get()=%{public}s", buf.get());
        LOGE("Resolve of the SCPDURL failed with = %ld", iRes);
        return DMC_FAILURE;
    }

    serviceInfo.controlUrl = buf.get();
    return DMC_SUCCESS;
}

dmc_int32 DmcParseServiceActions(IXML_Document* serviceDescripeDoc, std::vector<std::string> &actionVec)
{
    IXML_NodeList* actionList;
    dmc_uint32   ulLoop;
    dmc_uint32   ret;

    actionList = ixmlDocument_getElementsByTagName(serviceDescripeDoc, "action");
    if (nullptr == actionList) {
        LOGE(" Action list in Service Description is NULL");
        return DMC_FAILURE;
    }

    unsigned long ulNumAction = ixmlNodeList_length(actionList);

    for (ulLoop = 0; ulLoop < ulNumAction; ulLoop++) {
        IXML_Element* actionElement;

        actionElement = (IXML_Element*)ixmlNodeList_item(actionList, ulLoop);
        if (nullptr == actionElement) {
            LOGE(" Failed to Get The Action Node");
            ixmlNodeList_free(actionList);
            return DMC_FAILURE;
        }

        const char* actionName;
        actionName = DmcGetXmlElementValue(actionElement, "name");
        if (nullptr == actionName) {
            LOGW(" Action Name is NULL." );
            ixmlNodeList_free(actionList);
            return DMC_FAILURE;
        }
        LOGI(" actionName= %{public}s", actionName);
        actionVec.emplace_back(actionName);
    }
    ixmlNodeList_free(actionList);
    return DMC_SUCCESS;
}

dmc_int32 DmcParseStateVars(IXML_Document* serviceDescripeDoc, std::vector<std::string> &stateVarVec)
{
    IXML_NodeList* stateVarList;
    dmc_uint32   ulLoop;
    dmc_uint32   ret;

    stateVarList = ixmlDocument_getElementsByTagName(serviceDescripeDoc, "stateVariable");
    if (nullptr == stateVarList) {
        LOGE("StateVariable list in Service Description is NULL");
        return DMC_FAILURE;
    }

    unsigned long ulNumstvariable = ixmlNodeList_length(stateVarList);

    for (ulLoop = 0; ulLoop < ulNumstvariable; ulLoop++) {
        IXML_Element* stateVarEle = (IXML_Element*)ixmlNodeList_item(stateVarList, ulLoop);
        if (!stateVarEle) {
            LOGE(" Failed to Get The stateVariable Node");
            ixmlNodeList_free(stateVarList);
            return DMC_FAILURE;
        }

        const char* stateVarName = DmcGetXmlElementValue(stateVarEle, "name");
        if (nullptr == stateVarName) {
            LOGW(" StateVariable Name is NULL");
            ixmlNodeList_free(stateVarList);
            return DMC_FAILURE;
        }
        LOGI("stateVarName= %{public}s", stateVarName);
        stateVarVec.emplace_back(stateVarName);
    }
    ixmlNodeList_free(stateVarList);
    return DMC_SUCCESS;
}

dmc_int32 DmcParseServiceXml(const char* scpdurl, DmcServiceInfo &serviceInfo)
{
    IXML_Document* serviceDescripeDoc = nullptr;
    auto retVal = UpnpDownloadXmlDoc(scpdurl,
                                 &serviceDescripeDoc);
    if (UPNP_E_SUCCESS != retVal) {
        LOGE("Could not download service description! ret = %{public}ld", retVal);
        return DMC_FAILURE;
    }

    auto ret = DmcParseServiceActions(serviceDescripeDoc, serviceInfo.actionVec);
    if (ret != DMC_SUCCESS) {
        LOGE(" Failed to Parse Service Actions from Service Description,"
            "returned = %{public}lu", ret);
        ixmlDocument_free(serviceDescripeDoc);
        return ret;
    }

    ret = DmcParseStateVars(serviceDescripeDoc, serviceInfo.stateVarVec);
    ixmlDocument_free(serviceDescripeDoc);
    return ret;
}


dmc_int32 DmcGetScpdInfo(IXML_Element* serviceEle, const std::string &baseURL, DmcServiceInfo &serviceInfo)
{
    const char* scpdUrl = DmcGetXmlElementValue(serviceEle, "SCPDURL");
    if (nullptr == scpdUrl) {
        LOGE("Mandatory Field SCPDURL is Missing");
        return DMC_FAILURE;
    }

    auto resolvedScpdUrl = std::make_unique<char[]>(strlen(baseURL.c_str()) + strlen(scpdUrl) + 2);

    dmc_int32 iRes = UpnpResolveURL(baseURL.c_str(), scpdUrl, (char*)resolvedScpdUrl.get());
    if (UPNP_E_SUCCESS != iRes) {
        LOGE("Resolve of the SCPDURL failed with = %ld", iRes);
        return DMC_FAILURE;
    }
    LOGI("resolvedScpdUrl.get() = %{public}s", resolvedScpdUrl.get());
    auto ulRetVal = DmcParseServiceXml(resolvedScpdUrl.get(), serviceInfo);
    if (ulRetVal != DMC_SUCCESS) {
        return DMC_FAILURE;
    }

    return DMC_SUCCESS;
}

dmc_int32 DmcGetDevBaseInfo(IXML_Element* deviceElement,  DevDescInfoSptr descInfoSptr)
{
    const char* deviceType = DmcGetXmlElementValue(deviceElement, "deviceType");
    const char* udn        = DmcGetXmlElementValue(deviceElement, "UDN");
    const char* friendlyName = DmcGetXmlElementValue(deviceElement, "friendlyName");
    const char* manufacturer = DmcGetXmlElementValue(deviceElement, "manufacturer");
    const char* modelName       = DmcGetXmlElementValue(deviceElement, "modelName");

    LOGI("%{public}s ", deviceType);
    LOGI("%{public}s ", udn);
    LOGI("%{public}s ", friendlyName);
    LOGI("%{public}s ", manufacturer);
    LOGI("%{public}s ", modelName);

    if (udn == nullptr || deviceType == nullptr || friendlyName == nullptr) {
        LOGI("Invalid Device Description has"
            "been Received from peer device, Mandatory Elements are absent.");
        return DMC_FAILURE;
    }

    descInfoSptr->deviceType = deviceType;
    descInfoSptr->udn = udn;
    descInfoSptr->friendlyName = friendlyName;

    if (manufacturer == nullptr) {
        descInfoSptr->manufacturer = "unknown";
    }

    if (modelName == nullptr) {
        descInfoSptr->modelName = "unknown";
    }
    LOGI("finished");
    return DMC_SUCCESS;
}

dmc_int32 DmcParseServiceList(IXML_NodeList* serviceList, DevDescInfoSptr descInfoSptr)
{
    std::vector<DmcServiceInfo> &serviceVec = descInfoSptr->serviceVec;
    dmc_uint32 ulLoop;

    unsigned long ulNumService = ixmlNodeList_length(serviceList);
    for (ulLoop = 0; ulLoop < ulNumService; ulLoop++) {
        dmc_int32 iRes;
        IXML_Element* serviceEle = (IXML_Element*)ixmlNodeList_item(serviceList, ulLoop);
        if (nullptr == serviceEle) {
            LOGE("Failed to get the service Node");
            return DMC_FAILURE;
        }

        DmcServiceInfo serviceInfo;
        iRes = DmcGetServiceIdAndType(serviceEle, serviceInfo);
        if (iRes != DMC_SUCCESS) {
            continue;
        }

        iRes = DmcGetControlUrlAndEventSubUrl(serviceEle, descInfoSptr->baseUrl, serviceInfo);
        if (iRes != DMC_SUCCESS) {
            continue;
        }

        iRes = DmcGetScpdInfo(serviceEle, descInfoSptr->baseUrl , serviceInfo);
        if (iRes != DMC_SUCCESS)
        {
            continue;
        }
        serviceVec.emplace_back(std::move(serviceInfo));
    }

    return DMC_SUCCESS;
}

dmc_int32 DmcGetServiceInfo(IXML_Element* deviceElement, DevDescInfoSptr descInfoSptr)
{
    if (deviceElement == nullptr) {
        LOGE("nullptr");
        return DMC_FAILURE;
    }
    dmc_int32 ulRetVal;

    IXML_NodeList* serviceList = ixmlElement_getElementsByTagName(deviceElement, "service");
    if (serviceList == NULL) {
        LOGE("Device doesn't have any services to offer");
        return DMC_FAILURE;
    }

    ulRetVal = DmcParseServiceList(serviceList, descInfoSptr);
    if (ulRetVal != DMC_SUCCESS)
    {
        LOGE("Could not parse service description! ret = %{public}lu ", ulRetVal);

        ixmlNodeList_free(serviceList);
        return DMC_FAILURE;
    }

    ixmlNodeList_free(serviceList);

    return DMC_SUCCESS;
}

dmc_int32 DmcParseDescriptionXml(IXML_Document* devDescripeDoc, UpnpDiscovery* discovery,
    std::shared_ptr<DevDescInfo> descInfoSptr)
{
    LOGE("parse desc xml begin.");
    if (devDescripeDoc == nullptr || discovery == nullptr) {
        LOGE("null ptr.");
        return DMC_FAILURE;
    }
    dmc_int32 iRet = DMC_SUCCESS;

    const char* pcLocation = UpnpString_get_String(UpnpDiscovery_get_Location(discovery));
    if (pcLocation != nullptr) {
        LOGI("pcLocation=%{public}s.", pcLocation);
    }
    const char* baseURL = DmcParsebaseURL(devDescripeDoc);
    if (nullptr == baseURL) {
        LOGI("Base Url Not Found In Device Description.");
        baseURL = pcLocation;
    }
    LOGI("baseURL=%{public}s.", baseURL);
    descInfoSptr->baseUrl = baseURL;

    IXML_NodeList* deviceList = ixmlDocument_getElementsByTagName(devDescripeDoc, "device");
    if (nullptr == deviceList) {
        LOGI("Invalid Device Description has been"
            "Received from peer device no device information present ");
        return DMC_FAILURE;
    }

    dmc_uint32 iItemNum = ixmlNodeList_length(deviceList);
    LOGI("device num is %{public}d", iItemNum);

    if (iItemNum > 1) {
        LOGE("device num > 1 %{public}d", iItemNum);
        ixmlNodeList_free(deviceList);
        return DMC_FAILURE;
    }

    IXML_Element* deviceElement = (IXML_Element*) ixmlNodeList_item(deviceList, 0);

    if (deviceElement == nullptr) {
        LOGE("deviceElement nullptr");
        ixmlNodeList_free(deviceList);
        return DMC_FAILURE;
    }

    do {
        iRet = DmcGetDevBaseInfo(deviceElement, descInfoSptr);
        if (iRet != DMC_SUCCESS) {
            break;
        }

        if (!DmcIsDmrDevice(descInfoSptr->deviceType)) {
            LOGE("is not dmr device.");
            iRet = DMC_FAILURE;
            break;
        }

        iRet = DmcGetServiceInfo(deviceElement, descInfoSptr);
        if (iRet != DMC_SUCCESS) {
            break;
        }
    } while (0);

    descInfoSptr->iDeviceMaxAge = UpnpDiscovery_get_Expires(discovery);
    ixmlNodeList_free(deviceList);
    return iRet;
}

std::shared_ptr<DevDescInfo> DmcGetDescriptionInfo(const void *event)
{
    if (event == nullptr) {
        LOGI("null ptr when parse description xml.");
        return nullptr;
    }

    auto descXmlInfoSharedp= std::make_shared<DevDescInfo>();
    if (descXmlInfoSharedp == nullptr) {
        LOGE("nullptr");
        return nullptr;
    }

    IXML_Document* pDeviceDescripeDoc = nullptr;
    UpnpDiscovery* discovery = (UpnpDiscovery*)event;
    const char * xmlDocPath = UpnpString_get_String(UpnpDiscovery_get_Location(discovery));
    dmc_int32 iRetVal = UpnpDownloadXmlDoc(xmlDocPath, &pDeviceDescripeDoc);
    LOGI("download description, xmlDoc:%{public}s, ret = %{public}ld", xmlDocPath, iRetVal);
    if (UPNP_E_SUCCESS != iRetVal) {
        LOGE("Could not download device description, ret = %{public}ld ", iRetVal);
        return nullptr;
    }

    iRetVal = DmcParseDescriptionXml(pDeviceDescripeDoc, discovery, descXmlInfoSharedp);
    ixmlDocument_free(pDeviceDescripeDoc);
    if (iRetVal != DMC_SUCCESS) {
        return nullptr;
    }
    descXmlInfoSharedp->descUrl = xmlDocPath;
    return descXmlInfoSharedp;
}

}

